#include <test/jtx.h>
#include <test/jtx/trust.h>

#include <xrpl/protocol/Feature.h>

namespace ripple {

class Clawback_test : public beast::unit_test::suite
{
    template <class T>
    static std::string
    to_string(T const& t)
    {
        return boost::lexical_cast<std::string>(t);
    }

    // Helper function that returns the number of tickets held by an account.
    static std::uint32_t
    ticketCount(test::jtx::Env const& env, test::jtx::Account const& acct)
    {
        std::uint32_t ret{0};
        if (auto const sleAcct = env.le(acct))
            ret = sleAcct->at(~sfTicketCount).value_or(0);
        return ret;
    }

    // Helper function that returns the freeze status of a trustline
    static bool
    getLineFreezeFlag(
        test::jtx::Env const& env,
        test::jtx::Account const& src,
        test::jtx::Account const& dst,
        Currency const& cur)
    {
        if (auto sle = env.le(keylet::line(src, dst, cur)))
        {
            auto const useHigh = src.id() > dst.id();
            return sle->isFlag(useHigh ? lsfHighFreeze : lsfLowFreeze);
        }
        Throw<std::runtime_error>("No line in getLineFreezeFlag");
        return false;  // silence warning
    }

    void
    testAllowTrustLineClawbackFlag(FeatureBitset features)
    {
        testcase("Enable AllowTrustLineClawback flag");
        using namespace test::jtx;

        // Test that one can successfully set asfAllowTrustLineClawback flag.
        // If successful, asfNoFreeze can no longer be set.
        // Also, asfAllowTrustLineClawback cannot be cleared.
        {
            Env env(*this, features);
            Account alice{"alice"};

            env.fund(XRP(1000), alice);
            env.close();

            // set asfAllowTrustLineClawback
            env(fset(alice, asfAllowTrustLineClawback));
            env.close();
            env.require(flags(alice, asfAllowTrustLineClawback));

            // clear asfAllowTrustLineClawback does nothing
            env(fclear(alice, asfAllowTrustLineClawback));
            env.close();
            env.require(flags(alice, asfAllowTrustLineClawback));

            // asfNoFreeze cannot be set when asfAllowTrustLineClawback is set
            env.require(nflags(alice, asfNoFreeze));
            env(fset(alice, asfNoFreeze), ter(tecNO_PERMISSION));
            env.close();
        }

        // Test that asfAllowTrustLineClawback cannot be set when
        // asfNoFreeze has been set
        {
            Env env(*this, features);
            Account alice{"alice"};

            env.fund(XRP(1000), alice);
            env.close();

            env.require(nflags(alice, asfNoFreeze));

            // set asfNoFreeze
            env(fset(alice, asfNoFreeze));
            env.close();

            // NoFreeze is set
            env.require(flags(alice, asfNoFreeze));

            // asfAllowTrustLineClawback cannot be set if asfNoFreeze is set
            env(fset(alice, asfAllowTrustLineClawback), ter(tecNO_PERMISSION));
            env.close();

            env.require(nflags(alice, asfAllowTrustLineClawback));
        }

        // Test that asfAllowTrustLineClawback is not allowed when owner dir is
        // non-empty
        {
            Env env(*this, features);

            Account alice{"alice"};
            Account bob{"bob"};

            env.fund(XRP(1000), alice, bob);
            env.close();

            auto const USD = alice["USD"];
            env.require(nflags(alice, asfAllowTrustLineClawback));

            // alice issues 10 USD to bob
            env.trust(USD(1000), bob);
            env(pay(alice, bob, USD(10)));
            env.close();

            BEAST_EXPECT(ownerCount(env, alice) == 0);
            BEAST_EXPECT(ownerCount(env, bob) == 1);

            // alice fails to enable clawback because she has trustline with bob
            env(fset(alice, asfAllowTrustLineClawback), ter(tecOWNERS));
            env.close();

            // bob sets trustline to default limit and pays alice back to delete
            // the trustline
            env(trust(bob, USD(0), 0));
            env(pay(bob, alice, USD(10)));

            BEAST_EXPECT(ownerCount(env, alice) == 0);
            BEAST_EXPECT(ownerCount(env, bob) == 0);

            // alice now is able to set asfAllowTrustLineClawback
            env(fset(alice, asfAllowTrustLineClawback));
            env.close();
            env.require(flags(alice, asfAllowTrustLineClawback));

            BEAST_EXPECT(ownerCount(env, alice) == 0);
            BEAST_EXPECT(ownerCount(env, bob) == 0);
        }

        // Test that one cannot enable asfAllowTrustLineClawback when
        // featureClawback amendment is disabled
        {
            Env env(*this, features - featureClawback);

            Account alice{"alice"};

            env.fund(XRP(1000), alice);
            env.close();

            env.require(nflags(alice, asfAllowTrustLineClawback));

            // alice attempts to set asfAllowTrustLineClawback flag while
            // amendment is disabled. no error is returned, but the flag remains
            // to be unset.
            env(fset(alice, asfAllowTrustLineClawback));
            env.close();
            env.require(nflags(alice, asfAllowTrustLineClawback));

            // now enable clawback amendment
            env.enableFeature(featureClawback);
            env.close();

            // asfAllowTrustLineClawback can be set
            env(fset(alice, asfAllowTrustLineClawback));
            env.close();
            env.require(flags(alice, asfAllowTrustLineClawback));
        }
    }

    void
    testValidation(FeatureBitset features)
    {
        testcase("Validation");
        using namespace test::jtx;

        // Test that Clawback tx fails for the following:
        // 1. when amendment is disabled
        // 2. when asfAllowTrustLineClawback flag has not been set
        {
            Env env(*this, features - featureClawback);

            Account alice{"alice"};
            Account bob{"bob"};

            env.fund(XRP(1000), alice, bob);
            env.close();

            env.require(nflags(alice, asfAllowTrustLineClawback));

            auto const USD = alice["USD"];

            // alice issues 10 USD to bob
            env.trust(USD(1000), bob);
            env(pay(alice, bob, USD(10)));
            env.close();

            env.require(balance(bob, alice["USD"](10)));
            env.require(balance(alice, bob["USD"](-10)));

            // clawback fails because amendment is disabled
            env(claw(alice, bob["USD"](5)), ter(temDISABLED));
            env.close();

            // now enable clawback amendment
            env.enableFeature(featureClawback);
            env.close();

            // clawback fails because asfAllowTrustLineClawback has not been set
            env(claw(alice, bob["USD"](5)), ter(tecNO_PERMISSION));
            env.close();

            env.require(balance(bob, alice["USD"](10)));
            env.require(balance(alice, bob["USD"](-10)));
        }

        // Test that Clawback tx fails for the following:
        // 1. invalid flag
        // 2. negative STAmount
        // 3. zero STAmount
        // 4. XRP amount
        // 5. `account` and `issuer` fields are same account
        // 6. trustline has a balance of 0
        // 7. trustline does not exist
        {
            Env env(*this, features);

            Account alice{"alice"};
            Account bob{"bob"};

            env.fund(XRP(1000), alice, bob);
            env.close();

            // alice sets asfAllowTrustLineClawback
            env(fset(alice, asfAllowTrustLineClawback));
            env.close();
            env.require(flags(alice, asfAllowTrustLineClawback));

            auto const USD = alice["USD"];

            // alice issues 10 USD to bob
            env.trust(USD(1000), bob);
            env(pay(alice, bob, USD(10)));
            env.close();

            env.require(balance(bob, alice["USD"](10)));
            env.require(balance(alice, bob["USD"](-10)));

            // fails due to invalid flag
            env(claw(alice, bob["USD"](5)),
                txflags(0x00008000),
                ter(temINVALID_FLAG));
            env.close();

            // fails due to negative amount
            env(claw(alice, bob["USD"](-5)), ter(temBAD_AMOUNT));
            env.close();

            // fails due to zero amount
            env(claw(alice, bob["USD"](0)), ter(temBAD_AMOUNT));
            env.close();

            // fails because amount is in XRP
            env(claw(alice, XRP(10)), ter(temBAD_AMOUNT));
            env.close();

            // fails when `issuer` field in `amount` is not token holder
            // NOTE: we are using the `issuer` field for the token holder
            env(claw(alice, alice["USD"](5)), ter(temBAD_AMOUNT));
            env.close();

            // bob pays alice back, trustline has a balance of 0
            env(pay(bob, alice, USD(10)));
            env.close();

            // bob still owns the trustline that has 0 balance
            BEAST_EXPECT(ownerCount(env, alice) == 0);
            BEAST_EXPECT(ownerCount(env, bob) == 1);
            env.require(balance(bob, alice["USD"](0)));
            env.require(balance(alice, bob["USD"](0)));

            // clawback fails because because balance is 0
            env(claw(alice, bob["USD"](5)), ter(tecINSUFFICIENT_FUNDS));
            env.close();

            // set the limit to default, which should delete the trustline
            env(trust(bob, USD(0), 0));
            env.close();

            // bob no longer owns the trustline
            BEAST_EXPECT(ownerCount(env, alice) == 0);
            BEAST_EXPECT(ownerCount(env, bob) == 0);

            // clawback fails because trustline does not exist
            env(claw(alice, bob["USD"](5)), ter(tecNO_LINE));
            env.close();
        }
    }

    void
    testPermission(FeatureBitset features)
    {
        // Checks the tx submitter has the permission to clawback.
        // Exercises preclaim code
        testcase("Permission");
        using namespace test::jtx;

        // Clawing back from an non-existent account returns error
        {
            Env env(*this, features);

            Account alice{"alice"};
            Account bob{"bob"};

            // bob's account is not funded and does not exist
            env.fund(XRP(1000), alice);
            env.close();

            // alice sets asfAllowTrustLineClawback
            env(fset(alice, asfAllowTrustLineClawback));
            env.close();
            env.require(flags(alice, asfAllowTrustLineClawback));

            // bob, the token holder, does not exist
            env(claw(alice, bob["USD"](5)), ter(terNO_ACCOUNT));
            env.close();
        }

        // Test that trustline cannot be clawed by someone who is
        // not the issuer of the currency
        {
            Env env(*this, features);

            Account alice{"alice"};
            Account bob{"bob"};
            Account cindy{"cindy"};

            env.fund(XRP(1000), alice, bob, cindy);
            env.close();

            auto const USD = alice["USD"];

            // alice sets asfAllowTrustLineClawback
            env(fset(alice, asfAllowTrustLineClawback));
            env.close();
            env.require(flags(alice, asfAllowTrustLineClawback));

            // cindy sets asfAllowTrustLineClawback
            env(fset(cindy, asfAllowTrustLineClawback));
            env.close();
            env.require(flags(cindy, asfAllowTrustLineClawback));

            // alice issues 1000 USD to bob
            env.trust(USD(1000), bob);
            env(pay(alice, bob, USD(1000)));
            env.close();

            env.require(balance(bob, alice["USD"](1000)));
            env.require(balance(alice, bob["USD"](-1000)));

            // cindy tries to claw from bob, and fails because trustline does
            // not exist
            env(claw(cindy, bob["USD"](200)), ter(tecNO_LINE));
            env.close();
        }

        // When a trustline is created between issuer and holder,
        // we must make sure the holder is unable to claw back from
        // the issuer by impersonating the issuer account.
        //
        // This must be tested bidirectionally for both accounts because the
        // issuer could be either the low or high account in the trustline
        // object
        {
            Env env(*this, features);

            Account alice{"alice"};
            Account bob{"bob"};

            env.fund(XRP(1000), alice, bob);
            env.close();

            auto const USD = alice["USD"];
            auto const CAD = bob["CAD"];

            // alice sets asfAllowTrustLineClawback
            env(fset(alice, asfAllowTrustLineClawback));
            env.close();
            env.require(flags(alice, asfAllowTrustLineClawback));

            // bob sets asfAllowTrustLineClawback
            env(fset(bob, asfAllowTrustLineClawback));
            env.close();
            env.require(flags(bob, asfAllowTrustLineClawback));

            // alice issues 10 USD to bob.
            // bob then attempts to submit a clawback tx to claw USD from alice.
            // this must FAIL, because bob is not the issuer for this
            // trustline!!!
            {
                // bob creates a trustline with alice, and alice sends 10 USD to
                // bob
                env.trust(USD(1000), bob);
                env(pay(alice, bob, USD(10)));
                env.close();

                env.require(balance(bob, alice["USD"](10)));
                env.require(balance(alice, bob["USD"](-10)));

                // bob cannot claw back USD from alice because he's not the
                // issuer
                env(claw(bob, alice["USD"](5)), ter(tecNO_PERMISSION));
                env.close();
            }

            // bob issues 10 CAD to alice.
            // alice then attempts to submit a clawback tx to claw CAD from bob.
            // this must FAIL, because alice is not the issuer for this
            // trustline!!!
            {
                // alice creates a trustline with bob, and bob sends 10 CAD to
                // alice
                env.trust(CAD(1000), alice);
                env(pay(bob, alice, CAD(10)));
                env.close();

                env.require(balance(bob, alice["CAD"](-10)));
                env.require(balance(alice, bob["CAD"](10)));

                // alice cannot claw back CAD from bob because she's not the
                // issuer
                env(claw(alice, bob["CAD"](5)), ter(tecNO_PERMISSION));
                env.close();
            }
        }
    }

    void
    testEnabled(FeatureBitset features)
    {
        testcase("Enable clawback");
        using namespace test::jtx;

        // Test that alice is able to successfully clawback tokens from bob
        Env env(*this, features);

        Account alice{"alice"};
        Account bob{"bob"};

        env.fund(XRP(1000), alice, bob);
        env.close();

        auto const USD = alice["USD"];

        // alice sets asfAllowTrustLineClawback
        env(fset(alice, asfAllowTrustLineClawback));
        env.close();
        env.require(flags(alice, asfAllowTrustLineClawback));

        // alice issues 1000 USD to bob
        env.trust(USD(1000), bob);
        env(pay(alice, bob, USD(1000)));
        env.close();

        env.require(balance(bob, alice["USD"](1000)));
        env.require(balance(alice, bob["USD"](-1000)));

        // alice claws back 200 USD from bob
        env(claw(alice, bob["USD"](200)));
        env.close();

        // bob should have 800 USD left
        env.require(balance(bob, alice["USD"](800)));
        env.require(balance(alice, bob["USD"](-800)));

        // alice claws back 800 USD from bob again
        env(claw(alice, bob["USD"](800)));
        env.close();

        // trustline has a balance of 0
        env.require(balance(bob, alice["USD"](0)));
        env.require(balance(alice, bob["USD"](0)));
    }

    void
    testMultiLine(FeatureBitset features)
    {
        // Test scenarios where multiple trustlines are involved
        testcase("Multi line");
        using namespace test::jtx;

        // Both alice and bob issues their own "USD" to cindy.
        // When alice and bob tries to claw back, they will only
        // claw back from their respective trustline.
        {
            Env env(*this, features);

            Account alice{"alice"};
            Account bob{"bob"};
            Account cindy{"cindy"};

            env.fund(XRP(1000), alice, bob, cindy);
            env.close();

            // alice sets asfAllowTrustLineClawback
            env(fset(alice, asfAllowTrustLineClawback));
            env.close();
            env.require(flags(alice, asfAllowTrustLineClawback));

            // bob sets asfAllowTrustLineClawback
            env(fset(bob, asfAllowTrustLineClawback));
            env.close();
            env.require(flags(bob, asfAllowTrustLineClawback));

            // alice sends 1000 USD to cindy
            env.trust(alice["USD"](1000), cindy);
            env(pay(alice, cindy, alice["USD"](1000)));
            env.close();

            // bob sends 1000 USD to cindy
            env.trust(bob["USD"](1000), cindy);
            env(pay(bob, cindy, bob["USD"](1000)));
            env.close();

            // alice claws back 200 USD from cindy
            env(claw(alice, cindy["USD"](200)));
            env.close();

            // cindy has 800 USD left in alice's trustline after clawed by alice
            env.require(balance(cindy, alice["USD"](800)));
            env.require(balance(alice, cindy["USD"](-800)));

            // cindy still has 1000 USD in bob's trustline
            env.require(balance(cindy, bob["USD"](1000)));
            env.require(balance(bob, cindy["USD"](-1000)));

            // bob claws back 600 USD from cindy
            env(claw(bob, cindy["USD"](600)));
            env.close();

            // cindy has 400 USD left in bob's trustline after clawed by bob
            env.require(balance(cindy, bob["USD"](400)));
            env.require(balance(bob, cindy["USD"](-400)));

            // cindy still has 800 USD in alice's trustline
            env.require(balance(cindy, alice["USD"](800)));
            env.require(balance(alice, cindy["USD"](-800)));
        }

        // alice issues USD to both bob and cindy.
        // when alice claws back from bob, only bob's USD balance is
        // affected, and cindy's balance remains unchanged, and vice versa.
        {
            Env env(*this, features);

            Account alice{"alice"};
            Account bob{"bob"};
            Account cindy{"cindy"};

            env.fund(XRP(1000), alice, bob, cindy);
            env.close();

            auto const USD = alice["USD"];

            // alice sets asfAllowTrustLineClawback
            env(fset(alice, asfAllowTrustLineClawback));
            env.close();
            env.require(flags(alice, asfAllowTrustLineClawback));

            // alice sends 600 USD to bob
            env.trust(USD(1000), bob);
            env(pay(alice, bob, USD(600)));
            env.close();

            env.require(balance(alice, bob["USD"](-600)));
            env.require(balance(bob, alice["USD"](600)));

            // alice sends 1000 USD to cindy
            env.trust(USD(1000), cindy);
            env(pay(alice, cindy, USD(1000)));
            env.close();

            env.require(balance(alice, cindy["USD"](-1000)));
            env.require(balance(cindy, alice["USD"](1000)));

            // alice claws back 500 USD from bob
            env(claw(alice, bob["USD"](500)));
            env.close();

            // bob's balance is reduced
            env.require(balance(alice, bob["USD"](-100)));
            env.require(balance(bob, alice["USD"](100)));

            // cindy's balance is unchanged
            env.require(balance(alice, cindy["USD"](-1000)));
            env.require(balance(cindy, alice["USD"](1000)));

            // alice claws back 300 USD from cindy
            env(claw(alice, cindy["USD"](300)));
            env.close();

            // bob's balance is unchanged
            env.require(balance(alice, bob["USD"](-100)));
            env.require(balance(bob, alice["USD"](100)));

            // cindy's balance is reduced
            env.require(balance(alice, cindy["USD"](-700)));
            env.require(balance(cindy, alice["USD"](700)));
        }
    }

    void
    testBidirectionalLine(FeatureBitset features)
    {
        testcase("Bidirectional line");
        using namespace test::jtx;

        // Test when both alice and bob issues USD to each other.
        // This scenario creates only one trustline.
        // In this case, both alice and bob can be seen as the "issuer"
        // and they can send however many USDs to each other.
        // We test that only the person who has a negative balance from their
        // perspective is allowed to clawback
        Env env(*this, features);

        Account alice{"alice"};
        Account bob{"bob"};

        env.fund(XRP(1000), alice, bob);
        env.close();

        // alice sets asfAllowTrustLineClawback
        env(fset(alice, asfAllowTrustLineClawback));
        env.close();
        env.require(flags(alice, asfAllowTrustLineClawback));

        // bob sets asfAllowTrustLineClawback
        env(fset(bob, asfAllowTrustLineClawback));
        env.close();
        env.require(flags(bob, asfAllowTrustLineClawback));

        // alice issues 1000 USD to bob
        env.trust(alice["USD"](1000), bob);
        env(pay(alice, bob, alice["USD"](1000)));
        env.close();

        BEAST_EXPECT(ownerCount(env, alice) == 0);
        BEAST_EXPECT(ownerCount(env, bob) == 1);

        // bob is the holder, and alice is the issuer
        env.require(balance(bob, alice["USD"](1000)));
        env.require(balance(alice, bob["USD"](-1000)));

        // bob issues 1500 USD to alice
        env.trust(bob["USD"](1500), alice);
        env(pay(bob, alice, bob["USD"](1500)));
        env.close();

        BEAST_EXPECT(ownerCount(env, alice) == 1);
        BEAST_EXPECT(ownerCount(env, bob) == 1);

        // bob has negative 500 USD because bob issued 500 USD more than alice
        // bob can now been seen as the issuer, while alice is the holder
        env.require(balance(bob, alice["USD"](-500)));
        env.require(balance(alice, bob["USD"](500)));

        // At this point, both alice and bob are the issuers of USD
        // and can send USD to each other through one trustline

        // alice fails to clawback. Even though she is also an issuer,
        // the trustline balance is positive from her perspective
        env(claw(alice, bob["USD"](200)), ter(tecNO_PERMISSION));
        env.close();

        // bob is able to successfully clawback from alice because
        // the trustline balance is negative from his perspective
        env(claw(bob, alice["USD"](200)));
        env.close();

        env.require(balance(bob, alice["USD"](-300)));
        env.require(balance(alice, bob["USD"](300)));

        // alice pays bob 1000 USD
        env(pay(alice, bob, alice["USD"](1000)));
        env.close();

        // bob's balance becomes positive from his perspective because
        // alice issued more USD than the balance
        env.require(balance(bob, alice["USD"](700)));
        env.require(balance(alice, bob["USD"](-700)));

        // bob is now the holder and fails to clawback
        env(claw(bob, alice["USD"](200)), ter(tecNO_PERMISSION));
        env.close();

        // alice successfully claws back
        env(claw(alice, bob["USD"](200)));
        env.close();

        env.require(balance(bob, alice["USD"](500)));
        env.require(balance(alice, bob["USD"](-500)));
    }

    void
    testDeleteDefaultLine(FeatureBitset features)
    {
        testcase("Delete default trustline");
        using namespace test::jtx;

        // If clawback results the trustline to be default,
        // trustline should be automatically deleted
        Env env(*this, features);
        Account alice{"alice"};
        Account bob{"bob"};

        env.fund(XRP(1000), alice, bob);
        env.close();

        auto const USD = alice["USD"];

        // alice sets asfAllowTrustLineClawback
        env(fset(alice, asfAllowTrustLineClawback));
        env.close();
        env.require(flags(alice, asfAllowTrustLineClawback));

        // alice issues 1000 USD to bob
        env.trust(USD(1000), bob);
        env(pay(alice, bob, USD(1000)));
        env.close();

        BEAST_EXPECT(ownerCount(env, alice) == 0);
        BEAST_EXPECT(ownerCount(env, bob) == 1);

        env.require(balance(bob, alice["USD"](1000)));
        env.require(balance(alice, bob["USD"](-1000)));

        // set limit to default,
        env(trust(bob, USD(0), 0));
        env.close();

        BEAST_EXPECT(ownerCount(env, alice) == 0);
        BEAST_EXPECT(ownerCount(env, bob) == 1);

        // alice claws back full amount from bob, and should also delete
        // trustline
        env(claw(alice, bob["USD"](1000)));
        env.close();

        // bob no longer owns the trustline because it was deleted
        BEAST_EXPECT(ownerCount(env, alice) == 0);
        BEAST_EXPECT(ownerCount(env, bob) == 0);
    }

    void
    testFrozenLine(FeatureBitset features)
    {
        testcase("Frozen trustline");
        using namespace test::jtx;

        // Claws back from frozen trustline
        // and the trustline should remain frozen
        Env env(*this, features);
        Account alice{"alice"};
        Account bob{"bob"};

        env.fund(XRP(1000), alice, bob);
        env.close();

        auto const USD = alice["USD"];

        // alice sets asfAllowTrustLineClawback
        env(fset(alice, asfAllowTrustLineClawback));
        env.close();
        env.require(flags(alice, asfAllowTrustLineClawback));

        // alice issues 1000 USD to bob
        env.trust(USD(1000), bob);
        env(pay(alice, bob, USD(1000)));
        env.close();

        env.require(balance(bob, alice["USD"](1000)));
        env.require(balance(alice, bob["USD"](-1000)));

        // freeze trustline
        env(trust(alice, bob["USD"](0), tfSetFreeze));
        env.close();

        // alice claws back 200 USD from bob
        env(claw(alice, bob["USD"](200)));
        env.close();

        // bob should have 800 USD left
        env.require(balance(bob, alice["USD"](800)));
        env.require(balance(alice, bob["USD"](-800)));

        // trustline remains frozen
        BEAST_EXPECT(getLineFreezeFlag(env, alice, bob, USD.currency));
    }

    void
    testAmountExceedsAvailable(FeatureBitset features)
    {
        testcase("Amount exceeds available");
        using namespace test::jtx;

        // When alice tries to claw back an amount that is greater
        // than what bob holds, only the max available balance is clawed
        Env env(*this, features);
        Account alice{"alice"};
        Account bob{"bob"};

        env.fund(XRP(1000), alice, bob);
        env.close();

        auto const USD = alice["USD"];

        // alice sets asfAllowTrustLineClawback
        env(fset(alice, asfAllowTrustLineClawback));
        env.close();
        env.require(flags(alice, asfAllowTrustLineClawback));

        // alice issues 1000 USD to bob
        env.trust(USD(1000), bob);
        env(pay(alice, bob, USD(1000)));
        env.close();

        env.require(balance(bob, alice["USD"](1000)));
        env.require(balance(alice, bob["USD"](-1000)));

        // alice tries to claw back 2000 USD
        env(claw(alice, bob["USD"](2000)));
        env.close();

        // check alice and bob's balance.
        // alice was only able to claw back 1000 USD at maximum
        env.require(balance(bob, alice["USD"](0)));
        env.require(balance(alice, bob["USD"](0)));

        // bob still owns the trustline because trustline is not in default
        // state
        BEAST_EXPECT(ownerCount(env, alice) == 0);
        BEAST_EXPECT(ownerCount(env, bob) == 1);

        // set limit to default,
        env(trust(bob, USD(0), 0));
        env.close();

        // verify that bob's trustline was deleted
        BEAST_EXPECT(ownerCount(env, alice) == 0);
        BEAST_EXPECT(ownerCount(env, bob) == 0);
    }

    void
    testTickets(FeatureBitset features)
    {
        testcase("Tickets");
        using namespace test::jtx;

        // Tests clawback with tickets
        Env env(*this, features);
        Account alice{"alice"};
        Account bob{"bob"};

        env.fund(XRP(1000), alice, bob);
        env.close();

        auto const USD = alice["USD"];

        // alice sets asfAllowTrustLineClawback
        env(fset(alice, asfAllowTrustLineClawback));
        env.close();
        env.require(flags(alice, asfAllowTrustLineClawback));

        // alice issues 100 USD to bob
        env.trust(USD(1000), bob);
        env(pay(alice, bob, USD(100)));
        env.close();

        env.require(balance(bob, alice["USD"](100)));
        env.require(balance(alice, bob["USD"](-100)));

        // alice creates 10 tickets
        std::uint32_t ticketCnt = 10;
        std::uint32_t aliceTicketSeq{env.seq(alice) + 1};
        env(ticket::create(alice, ticketCnt));
        env.close();
        std::uint32_t const aliceSeq{env.seq(alice)};
        BEAST_EXPECT(ticketCount(env, alice) == ticketCnt);
        BEAST_EXPECT(ownerCount(env, alice) == ticketCnt);

        while (ticketCnt > 0)
        {
            // alice claws back 5 USD using a ticket
            env(claw(alice, bob["USD"](5)), ticket::use(aliceTicketSeq++));
            env.close();

            ticketCnt--;
            BEAST_EXPECT(ticketCount(env, alice) == ticketCnt);
            BEAST_EXPECT(ownerCount(env, alice) == ticketCnt);
        }

        // alice clawed back 50 USD total, trustline has 50 USD remaining
        env.require(balance(bob, alice["USD"](50)));
        env.require(balance(alice, bob["USD"](-50)));

        // Verify that the account sequence numbers did not advance.
        BEAST_EXPECT(env.seq(alice) == aliceSeq);
    }

    void
    testWithFeats(FeatureBitset features)
    {
        testAllowTrustLineClawbackFlag(features);
        testValidation(features);
        testPermission(features);
        testEnabled(features);
        testMultiLine(features);
        testBidirectionalLine(features);
        testDeleteDefaultLine(features);
        testFrozenLine(features);
        testAmountExceedsAvailable(features);
        testTickets(features);
    }

public:
    void
    run() override
    {
        using namespace test::jtx;
        FeatureBitset const all{testable_amendments()};

        testWithFeats(all - featureMPTokensV1);
        testWithFeats(all);
    }
};

BEAST_DEFINE_TESTSUITE(Clawback, app, ripple);
}  // namespace ripple
